package middleware

import (
	"context"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/goccy/go-json"
	"github.com/mojocn/base64Captcha"
	"github.com/spf13/afero"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/core/redisdb"
	"gitlab.local/DO-module/new-filemanage-module/src/service/phpsessgo"
	"gitlab.local/DO-module/new-filemanage-module/src/service/phpsessgo/phptype"
	"gitlab.local/DO-module/new-filemanage-module/src/service/redis"
	"gitlab.local/DO-module/new-filemanage-module/src/service/taskhook"
	"gitlab.local/TerraMaster/tos-modules/core/acl"
	"gitlab.local/TerraMaster/tos-modules/database"
	"gitlab.local/TerraMaster/tos-modules/database/sqlitemodel"
	"gitlab.local/golibrary/resp"
	"gitlab.local/golibrary/sysconfig"
	"gitlab.local/golibrary/utils"
	"reflect"
	"time"
)

type LoginOption struct {
	UserName string `json:"username"`
	PassWord string `json:"password"`
	Remember bool   `json:"remember"`
	NeedCode string `json:"code"`
}

type AccountBlock struct {
	BlockAde        bool `json:"blockade" ini:"blockade"`                 //是否启动帐号安全策略
	BlockAdeDays    int  `json:"blockade_days" ini:"blockade_days"`       //帐号封锁时间（天）
	BlockAdeExpired bool `json:"blockade_expired" ini:"blockade_expired"` //帐号封锁是否有期限
	LoginTimes      int  `json:"login_times" ini:"login_times"`           //尝试登录次数
	InMinutes       int  `json:"in_mins" ini:"in_mins"`                   //最短几分钟内的安全策略
}

type Authorization struct {
	Username string `json:"username"`
	Password string `json:"password"`
	Token    string `json:"token"`
}

type Record struct {
	Count    int       `json:"count"`    //尝试登录次数
	Time     time.Time `json:"time"`     //上次尝试时间
	NeedCode bool      `json:"needcode"` //是否需要验证码
}

type BanAccount struct {
	Client    string    `json:"client"`
	BlockTime time.Time `json:"block_time"` //被ban的时间
}

type AuthChecker interface {
	LoginAuthed() gin.HandlerFunc
	CheckUserPasswd() gin.HandlerFunc
	UpdateSession(c *gin.Context, act string, userInfo sqlitemodel.UserTable) error
	SetSession(c *gin.Context, key string, value any) error
	GetSession(c *gin.Context, key string) string
	Cors() gin.HandlerFunc
	AuthService(username string, password string) error
}

type MiddleWare struct {
	dbGetter      database.DbGetter
	sessionManage phpsessgo.SessionManager
	fs            afero.Fs
	captcha       *base64Captcha.Captcha
	hookApi       taskhook.HookApi
	acl           acl.FolderAttrService
	cfg           sysconfig.Getter
	wan           struct {
		IP      string
		PreTime time.Time
	}
}

func New(getter database.DbGetter,
	fs afero.Fs,
	store redisdb.Getter,
	api taskhook.HookApi,
	acl acl.FolderAttrService,
) *MiddleWare {
	manager := redis.NewRedisSessionManager(store.DbGet(), phpsessgo.SessionManagerConfig{
		Expiration:     24 * time.Minute,
		CookiePath:     "/",
		CookieHttpOnly: true,
	})
	middle := &MiddleWare{
		dbGetter:      getter,
		sessionManage: manager,
		fs:            fs,
		hookApi:       api,
		acl:           acl,
		cfg:           sysconfig.New(),
	}
	//default Redis TTL for a MaxAge == 0 session 单位:秒(second)
	middle.loadMaxAge()
	utils.WatchFile(constant.AccountSecret, func() {
		middle.loadMaxAge()
	})
	middle.getWanIP()

	return middle
}

func GetUserName(c *gin.Context) (string, error) {
	start := time.Now()
	kodUser, ok := c.Get(constant.ContextUserInfo)
	if !ok {
		//无法获取当前登录用户信息时，直接返回错误403
		resp.Output(c, "", errors.New("login timeout"), time.Now().Sub(start))
		c.Abort()
		return "", nil
	}
	var user struct {
		Username string `json:"username"`
	}
	err := json.Unmarshal(kodUser.([]byte), &user)
	if err != nil {
		err = errors.New("GetUserName Unmarshal fail, err: " + err.Error())
	}
	return user.Username, err
}

func _GetUserName(ctx context.Context) (string, error) {
	kodUser := ctx.Value(constant.ContextUserInfo)
	if kodUser == nil {
		// 无法获取当前登录用户信息时，直接返回错误403
		return "", errors.New("login timeout")
	}

	var user struct {
		Username string `json:"username"`
	}
	err := json.Unmarshal(kodUser.([]byte), &user)
	if err != nil {
		err = errors.New("GetUserName Unmarshal fail, err: " + err.Error())
	}

	return user.Username, err
}

func ParseTosUser(c *gin.Context) *sqlitemodel.UserTable {
	start := time.Now()
	kodUser, ok := c.Get(constant.ContextUserInfo)
	if !ok {
		//无法获取当前登录用户信息时，直接返回错误403
		resp.Output(c, "", errors.New("login timeout"), time.Now().Sub(start))
		c.Abort()
		return nil
	}
	user := sqlitemodel.UserTable{}
	err := json.Unmarshal(kodUser.([]byte), &user)
	if err != nil {
		return nil
	}
	return &user
}

func SetCookie(c *gin.Context, name, value string, maxAge int) {
	c.SetCookie(name, value, maxAge, "", "", false, false)
}

func PhpValue2Map(value phptype.Value) interface{} {
	switch value.(type) {
	case phptype.Array:
		isArray := 0
		data := make(map[string]interface{})
		data1 := make([]interface{}, 0)
		for k, v := range value.(phptype.Array) {
			if isArray == 0 {
				if reflect.ValueOf(k).Kind() == reflect.Int {
					isArray = 1
				} else {
					isArray = 2
				}
			}
			if isArray == 1 {
				data1 = append(data1, PhpValue2Map(v))
			} else {
				key := fmt.Sprintf("%v", k)
				data[key] = PhpValue2Map(v)
			}
		}
		if isArray == 1 {
			return data1
		} else {
			return data
		}
	default:
		var data interface{}
		dst := reflect.ValueOf(value)
		if dst.IsValid() {
			data = dst.Interface()
		}
		return data
	}
}

func Struct2Map(obj interface{}) (phptype.Array, error) {
	body, err := json.Marshal(obj)
	if err != nil {
		return nil, err
	}
	data := make(map[string]interface{})
	err = json.Unmarshal(body, &data)
	if err != nil {
		return nil, err
	}
	result := make(phptype.Array)
	for k, v := range data {
		dst := reflect.ValueOf(v)
		if dst.Kind() == reflect.Slice {
			if len(v.([]interface{})) > 0 {
				value := make(phptype.Array)
				for index, i := range v.([]interface{}) {
					value[index] = phptype.Value(i)
				}
				result[k] = value
			}
		} else {
			result[k] = v
		}
	}
	return result, nil
}
